#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <iostream>

#include <nng/nng.h>
#include <nng/protocol/pubsub0/pub.h>
#include <nng/protocol/pubsub0/sub.h>
#include <nng/supplemental/tls/tls.h>
#include <nng/transport/zerotier/zerotier.h>

#define PROXY_FRONT_URL "tcp://localhost:3327"
// #define PROXY_BACK_URL "tcp://localhost:3328"

void
panic_on_error(int should_panic, const char *format, ...)
{
	if (should_panic) {
		va_list args;
		va_start(args, format);
		vprintf(format, args);
		va_end(args);
		exit(EXIT_FAILURE);
	}
}


static void
fatal(const char *msg, ...)
{
	va_list ap;
	va_start(ap, msg);
	vfprintf(stderr, msg, ap);
	va_end(ap);
	fprintf(stderr, "\n");
	exit(1);
}


void
sendloop(nng_socket sock)
{
	int iters = 0;

	const char* data="This is data to send: A message from behind the Great Wall.";
	size_t datalen = strlen(data);
	int delay = 0;
	//nng_duration interval  = NNG_DURATION_INFINITE;
	nng_duration interval  = 500;
	int count = 0;

	if (data == NULL) {
		fatal("No data to send (specify with --data or --file)");
	}
	if (delay > 0) {
		nng_msleep(delay);
	}

	for (;;) {
		int          rv;
		nng_msg *    msg;
		nng_time     start;
		nng_time     end;
		nng_duration delta;

		start = nng_clock();
		if (((rv = nng_msg_alloc(&msg, 0)) != 0) ||
		    ((rv = nng_msg_append(msg, data, datalen)) != 0)) {
			fatal(nng_strerror(rv));
		}
		if ((rv = nng_sendmsg(sock, msg, 0)) != 0) {
			fatal("Send error: %s", nng_strerror(rv));
		}
		end   = nng_clock();
		delta = (nng_duration)(end - start);

		iters++;
		// By default, we don't loop.
		if (((interval < 0) && (count == 0)) ||
		    ((count > 0) && (iters >= count))) {
			break;
		}

		// We sleep, but we account for time spent, so that our
		// interval appears more or less constant.  Of course
		// if we took more than the interval here, then we skip
		// the sleep altogether.
		if ((delta >= 0) && (delta < interval)) {
			nng_msleep(interval - delta);
		}
	}
	// Wait a bit to give queues a chance to drain.
	nng_msleep(200);
}


static void
configtls(nng_tls_config *tls)
{
	int rv = 0;
	int insecure = 1;
	char * certfile = NULL;
	char * keyfile = NULL;
	char * cacert = NULL;

	if (insecure) {
		rv = nng_tls_config_auth_mode(tls, NNG_TLS_AUTH_MODE_NONE);
	}
	if ((rv == 0) && (certfile != NULL)) {
		keyfile = keyfile ? keyfile : certfile;
		rv = nng_tls_config_own_cert(tls, certfile, keyfile, NULL);
	}
	if ((rv == 0) && (cacert != NULL)) {
		rv = nng_tls_config_ca_chain(tls, cacert, NULL);
	}
	if (rv != 0) {
		fatal("Unable to configure TLS: %s", nng_strerror(rv));
	}
}



int
main()
{
	nng_socket sock = NNG_SOCKET_INITIALIZER;
	int        ret            = 0;

	//
	//  First we need some nng sockets. Not to be confused with network
	//  sockets
	//
	ret = nng_pub0_open(&sock);
	panic_on_error(ret, "Failed to open socket: %s\n", nng_strerror(ret));

	//
	//  Now we need to set up a listener for each socket so that they have
	//  addresses
	//

	{
		int rv = 0;
		nng_dialer d;
		nng_tls_config *tls;
		const char * zthome = NULL;
		int async = 0;
		char * act = NULL;
		int verbose = 1;
		int OPT_VERBOSE = 1;

		rv = nng_dialer_create(&d, sock, PROXY_FRONT_URL);
		if (rv != 0) {
			fatal("Unable to create dialer for %s: %s",
				  PROXY_FRONT_URL, nng_strerror(rv));
		}
		rv = nng_dialer_getopt_ptr(
			d, NNG_OPT_TLS_CONFIG, (void **) &tls);
		if (rv == 0) {
			configtls(tls);
		} else if (rv != NNG_ENOTSUP) {
			fatal("Unable to get TLS config: %s",
				nng_strerror(rv));
		}
		if (zthome != NULL) {
			rv = nng_dialer_setopt(d, NNG_OPT_ZT_HOME,
				zthome, strlen(zthome) + 1);
			if ((rv != 0) && (rv != NNG_ENOTSUP)) {
				fatal("Unable to set ZT home: %s",
					nng_strerror(rv));
			}
		}
		rv  = nng_dialer_start(d, async);
		act = "dial";
		if ((rv == 0) && (verbose == OPT_VERBOSE)) {
			char   ustr[256];
			size_t sz;
			sz = sizeof(ustr);
			if (nng_dialer_getopt(
					d, NNG_OPT_URL, ustr, &sz) == 0) {
				printf("Connected to: %s\n", ustr);
			}
		}
	}

	//
	//  Finally let nng do the forwarding/proxying
	//

	// ret = nng_device(sock_front_end, sock_back_end);
	// panic_on_error(
	//     ret, "f-b nng_device returned %d: %s\n", ret, nng_strerror(ret));

	sendloop(sock);

	printf("done");
	return 0;
}